// Package format formats Slackdump conversations, channels and users as
// human-readable text or CSV.
package format

import (
	"context"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/rusq/slackdump/v3/source"

	"github.com/rusq/fsadapter"

	"github.com/rusq/slackdump/v3/cmd/slackdump/internal/bootstrap"
	"github.com/rusq/slackdump/v3/cmd/slackdump/internal/cfg"
	"github.com/rusq/slackdump/v3/cmd/slackdump/internal/golang/base"
	"github.com/rusq/slackdump/v3/internal/format"
	"github.com/rusq/slackdump/v3/internal/structures"
)

var CmdFormat = &base.Command{
	Run:       runFormat,
	UsageLine: "slackdump format [flags] <format> <file.json>",
	Short:     "converts the slackdump dump files to a human readable format",
	Long: `
# Format Command

Format command formats the json files generated by "slackdump dump" command to
a human readable format.  The command takes the format type and the file to
convert as arguments.
`, // TODO: add more info
	CustomFlags: false,
	FlagMask:    cfg.OmitAll &^ cfg.OmitOutputFlag,
	PrintFlags:  true,
	RequireAuth: true,
}

// custom command flags
var flgOnline bool

func init() {
	CmdFormat.Flag.BoolVar(&flgOnline, "online", false, "get online users")
}

func runFormat(ctx context.Context, cmd *base.Command, args []string) error {
	if len(args) < 1 {
		base.SetExitStatus(base.SInvalidParameters)
		return fmt.Errorf("must specify output format (supported: %v)", format.All())
	}

	// determining the conversion type.
	var convType format.Type
	var formatter format.Formatter
	if err := convType.Set(args[0]); err != nil {
		base.SetExitStatus(base.SInvalidParameters)
		return err
	} else {
		var ok bool
		formatterInit, ok := convType.FormatFunc()
		if !ok {
			base.SetExitStatus(base.SInvalidParameters)
			return errors.New("unknown converter type")
		}
		formatter = formatterInit()
	}

	var input string
	if len(args) > 1 {
		input = args[1]
	}
	var el *structures.EntityList
	if len(args) > 2 {
		var err error
		el, err = structures.NewEntityList(args[2:])
		if err != nil {
			base.SetExitStatus(base.SInvalidParameters)
			return err
		}
	}

	fi, err := os.Stat(input)
	if err != nil {
		base.SetExitStatus(base.SUserError)
		return err
	}

	start := time.Now()
	if fi.IsDir() || strings.ToLower(filepath.Ext(input)) == ".zip" {
		src, err := source.Load(ctx, input)
		if err != nil {
			base.SetExitStatus(base.SUserError)
			return err
		}
		defer src.Close()

		if err := bootstrap.AskOverwrite(cfg.Output); err != nil {
			return err
		}

		fsa, err := fsadapter.New(cfg.Output)
		if err != nil {
			base.SetExitStatus(base.SUserError)
			return err
		}
		defer fsa.Close()

		if err := formatSrc(ctx, fsa, src, formatter, el); err != nil {
			base.SetExitStatus(base.SApplicationError)
		}
		cfg.Log.InfoContext(ctx, "formatted source", "format", convType.String(), "output", cfg.Output, "took", time.Since(start).String())
	} else if strings.ToLower(filepath.Ext(input)) == ".json" {
		f, err := os.Open(input)
		if err != nil {
			base.SetExitStatus(base.SUserError)
		}
		if err := formatJSONfile(ctx, os.Stdout, formatter, f); err != nil {
			if errors.Is(err, ErrUnknown) || errors.Is(err, ErrInvalidFormat) {
				base.SetExitStatus(base.SInvalidParameters)
			} else {
				base.SetExitStatus(base.SApplicationError)
			}
			return err
		}
		cfg.Log.InfoContext(ctx, "formatted file", "format", convType.String(), "took", time.Since(start).String())
	} else {
		base.SetExitStatus(base.SInvalidParameters)
		return errors.New("unsupported input format")
	}
	return nil
}
